#include "csapp.h"
#include <stdbool.h>

/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define MAX_CACHE 10

#define NTHREADS 4
#define SBUFSIZE 16

/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";

typedef struct URL
{
    char host[MAXLINE];  // 主机名
    char port[MAXLINE];  // 端口号
    char path[MAXLINE];  // 路径
} URL;

typedef struct
{
    int *buf;    /* 缓冲区数组 */
    int n;       /* 最大槽位数 */
    int front;   /* buf[(front+1)%n] 是第一个项目 */
    int rear;    /* buf[rear%n] 是最后一个项目 */
    sem_t mutex; /* 保护对 buf 的访问 */
    sem_t slots; /* 可用槽位计数 */
    sem_t items; /* 可用项目计数 */
} sbuf_t;

/**
 * @struct Cache
 * @brief 缓存结构体
 * 
 * 用于存储缓存的相关信息。
 */
typedef struct Cache
{
    bool empty; /**< 缓存是否为空 */
    URL url; /**< 缓存对应的URL */
    char data[MAX_OBJECT_SIZE]; /**< 缓存的数据 */
    int lru; /**< 最近最少使用的计数 */
    int read_cnt; /**< 读取计数 */
    sem_t mutex, w; /**< 互斥锁和写锁 */
} Cache;

sbuf_t sbuf; /**< 信号量缓冲区 */

Cache ca[MAX_CACHE]; /**< 缓存数组 */

/**
 * @brief 判断两个URL是否相等
 * 
 * @param a 第一个URL
 * @param b 第二个URL
 * @return true 如果两个URL相等
 * @return false 如果两个URL不相等
 */
bool urlEqual(const URL *a, const URL *b);

/**
 * @brief 将URL b的值复制给URL a
 * 
 * @param a 目标URL
 * @param b 源URL
 */
void urlCopy(URL *a, const URL *b);

/**
 * @brief 初始化信号量缓冲区
 * 
 * @param sp 信号量缓冲区指针
 * @param n 缓冲区大小
 */
void sbuf_init(sbuf_t *sp, int n);

/**
 * @brief 销毁信号量缓冲区
 * 
 * @param sp 信号量缓冲区指针
 */
void sbuf_deinit(sbuf_t *sp);

/**
 * @brief 向信号量缓冲区插入元素
 * 
 * @param sp 信号量缓冲区指针
 * @param item 要插入的元素
 */
void sbuf_insert(sbuf_t *sp, int item);

/**
 * @brief 从信号量缓冲区移除元素
 * 
 * @param sp 信号量缓冲区指针
 * @return int 移除的元素
 */
int sbuf_remove(sbuf_t *sp);

/**
 * @brief 处理客户端请求
 * 
 * @param connfd 客户端连接套接字
 */
void doit(int connfd);

/**
 * @brief 解析URL字符串
 * 
 * @param s URL字符串
 * @param url 解析后的URL结构体
 */
void parseUrl(char *s, URL *url);

/**
 * @brief 线程函数
 * 
 */
void thread();

/**
 * @brief 开始读取缓存
 * 
 * @param c 缓存指针
 */
void readBegin(Cache *c);

/**
 * @brief 结束读取缓存
 * 
 * @param c 缓存指针
 */
void readEnd(Cache *c);

/**
 * @brief 开始写入缓存
 * 
 * @param c 缓存指针
 */
void writeBegin(Cache *c);

/**
 * @brief 结束写入缓存
 * 
 * @param c 缓存指针
 */
void writeEnd(Cache *c);

/**
 * @brief 初始化缓存数组
 */
void initCache();

/**
 * @brief 根据URL获取缓存
 * 
 * @param url URL指针
 * @return Cache* 缓存指针
 */
Cache *getCache(URL *url);

/**
 * @brief 插入缓存
 * 
 * @param url URL指针
 * @param data 缓存数据
 */
void insCache(URL *url, char *data);

/**
 * @brief 更新最近最少使用计数
 * 
 * @param c 缓存指针
 */
void updateLRU(Cache *c);

/**
 * @brief 填充缓存
 * 
 * @param c 缓存指针
 * @param url URL指针
 * @param data 缓存数据
 */
void fillCache(Cache *c, URL *url, char *data);

int main(int argc, char **argv)
{
    int listenfd, connfd;//监听端口
    char hostname[MAXLINE], port[MAXLINE];//主机名和端口
    socklen_t clientlen;//客户端地址长度
    struct sockaddr_storage clientaddr;//客户端地址
    pthread_t tid;//线程ID

    //检查参数个数
    if (argc != 2)
    {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(1);
    }
    //初始化信号量缓冲区
    sbuf_init(&sbuf, SBUFSIZE);

    //创建线程
    for (int i = 0; i < NTHREADS; ++i)
        Pthread_create(&tid, NULL, thread, NULL);
    initCache();//初始化缓存数组
    //打开监听端口
    listenfd = Open_listenfd(argv[1]);
    while (1)//循环处理客户端请求
    {
        clientlen = sizeof(clientaddr); // 设置客户端地址结构体的大小
        connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); // 接受客户端的连接请求，返回一个新的连接描述符
        Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0); // 获取客户端的主机名和端口号
        printf("Accepted connection from (%s, %s)\n", hostname, port); // 打印出接受的客户端的主机名和端口号
        sbuf_insert(&sbuf, connfd); // 将新的连接描述符插入到缓冲区中，等待线程来处理
    }
}

void thread() // 工作线程的主体函数
{
    Pthread_detach(pthread_self()); // 分离当前线程，当线程结束时，资源会自动回收
    while (1) // 无限循环，使得线程可以处理多个连接
    {
        int connfd = sbuf_remove(&sbuf); // 从缓冲区中移除一个连接描述符
        doit(connfd); // 处理这个连接的请求
        Close(connfd); // 关闭这个连接
    }
}

void doit(int connfd) // 处理客户端请求的函数，参数connfd是连接描述符
{
    rio_t rio; // 定义一个rio_t结构体，用于读写
    char line[MAXLINE]; // 定义一个字符数组，用于存储读取的行
    Rio_readinitb(&rio, connfd); // 初始化rio结构体，将连接描述符关联到rio

    URL url; // 定义一个URL结构体，用于存储解析出的URL
    char data[MAXLINE]; // 定义一个字符数组，用于存储客户端的请求
    readClient(&rio, &url, data); // 读取客户端的请求，解析URL，生成向服务器发送的请求

    Cache *c; // 定义一个缓存指针
    if ((c = getCache(&url)) != NULL) // 如果在缓存中找到了匹配的项
    {
        Rio_writen(connfd, c->data, strlen(c->data)); // 将缓存的数据写回给客户端
        return; // 结束函数
    }

    int serverfd = open_clientfd(url.host, url.port); // 打开到服务器的连接
    if (serverfd < 0) // 如果连接失败
        printf("Connection failed!\n"); // 打印错误信息

    rio_readinitb(&rio, serverfd); // 将服务器的连接描述符关联到rio
    Rio_writen(serverfd, data, strlen(data)); // 将客户端的请求写入到服务器

    int len, sumlen = 0; // 定义长度和总长度
    char cache[MAX_OBJECT_SIZE], *cp = cache; // 定义一个缓存数组和一个指针
    while ((len = Rio_readlineb(&rio, line, MAXLINE)) > 0) // 从服务器读取数据，直到读取完毕
    {
        Rio_writen(connfd, line, len); // 将数据写回给客户端
        sumlen += len; // 更新总长度
        if (sumlen < MAX_OBJECT_SIZE) // 如果总长度小于缓存的最大大小
        {
            strcpy(cp, line); // 将数据复制到缓存
            cp += len; // 更新指针
        }
    }
    insCache(&url, cache); // 将数据插入到缓存

    Close(serverfd); // 关闭到服务器的连接
}
void parseUrl(char *s, URL *url)
{
    // 查找URL中的"//"，如果存在，将s指向"//"后的字符
    char *ptr = strstr(s, "//");
    if (ptr != NULL)
        s = ptr + 2;

    // 查找URL中的'/'，如果存在，将其后的字符复制到url的path中，并将'/'替换为'\0'
    ptr = strchr(s, '/');
    if (ptr != NULL)
    {
        strcpy(url->path, ptr);
        *ptr = '\0';
    }
    else
        strcpy(url->path, "/"); // 如果URL中没有'/'，则将url的path设置为"/"

    // 查找URL中的':'，如果存在，将其后的字符复制到url的port中，并将':'替换为'\0'
    ptr = strchr(s, ':');
    if (ptr != NULL)
    {
        strcpy(url->port, ptr + 1);
        *ptr = '\0';
    }
    else
        strcpy(url->port, "80"); // 如果URL中没有':'，则将url的port设置为"80"

    // 将s剩余的部分复制到url的host中
    strcpy(url->host, s);
}

void readClient(rio_t *rio, URL *url, char *data)
{
    // 定义一些临时变量
    char host[MAXLINE];
    char line[MAXLINE];
    char other[MAXLINE];
    char method[MAXLINE], urlstr[MAXLINE], version[MAXLINE];

    // 从rio中读取一行，解析出HTTP请求的方法、URL和版本
    Rio_readlineb(rio, line, MAXLINE);
    sscanf(line, "%s %s %s\n", method, urlstr, version);
    parseUrl(urlstr, url); // 解析URL

    // 构造Host头部
    sprintf(host, "Host: %s\r\n", url->host);

    // 读取并处理HTTP请求的其他头部
    while (Rio_readlineb(rio, line, MAXLINE) > 0)
    {
        if (strcmp(line, "\r\n") == 0) // 如果读到空行，表示头部结束
            break;
        if (strncmp(line, "Host", 4) == 0) // 如果是Host头部，使用新的值
            strcpy(host, line);
        if (strncmp(line, "User-Agent", 10) &&
            strncmp(line, "Connection", 10) &&
            strncmp(line, "Proxy-Connection", 16)) // 如果不是这三种头部，添加到other中
            strcat(other, line);
    }

    // 构造HTTP请求
    sprintf(data, "%s %s HTTP/1.0\r\n"
                  "%s%s"
                  "Connection: close\r\n"
                  "Proxy-Connection: close\r\n"
                  "%s\r\n",
            method, url->path, host, user_agent_hdr, other);
}

void initCache()
{
    // 初始化每个缓存项
    for (int i = 0; i < MAX_CACHE; ++i)
    {
        ca[i].empty = 1; // 设置为空
        Sem_init(&ca[i].mutex, 0, 1); // 初始化互斥信号量
        Sem_init(&ca[i].w, 0, 1); // 初始化写者信号量
    }
}

Cache *getCache(URL *url)
{
    // 查找URL对应的缓存项
    Cache *ans = NULL;
    for (int i = 0; i < MAX_CACHE && ans == NULL; ++i)
    {
        readBegin(&ca[i]); // 开始读取
        if (!ca[i].empty && urlEqual(&ca[i].url, url)) // 如果缓存项不为空且URL匹配，设置为答案
            ans = &ca[i];
        readEnd(&ca[i]); // 结束读取
    }
    if (ans != NULL)
        updateLRU(ans); // 如果找到了缓存项，更新其LRU值
    return ans;
}

void insCache(URL *url, char *data)
{
    // 查找一个空的缓存项
    Cache *pos = NULL;
    for (int i = 0; i < MAX_CACHE && pos == NULL; ++i)
    {
        readBegin(&ca[i]); // 开始读取
        if (ca[i].empty) // 如果缓存项为空，设置为目标位置
            pos = &ca[i];
        readEnd(&ca[i]); // 结束读取
    }

    // 如果没有找到空的缓存项，选择一个LRU值最小的缓存项
    if (pos == NULL)
    {
        int minLRU = __INT_MAX__;
        for (int i = 0; i < MAX_CACHE; ++i)
        {
            readBegin(&ca[i]); // 开始读取
            if (!ca[i].empty && ca[i].lru < minLRU) // 如果缓存项不为空且LRU值小于当前最小值，设置为目标位置
            {
                minLRU = ca[i].lru;
                pos = &ca[i];
            }
            readEnd(&ca[i]); // 结束读取
        }
    }

    // 填充目标位置的缓存项
    fillCache(pos, url, data);
}

void updateLRU(Cache *c)
{
    // 更新缓存项的LRU值
    static int clock = 0;
    writeBegin(c); // 开始写入
    c->lru = ++clock; // LRU值设为当前时钟值
    writeEnd(c); // 结束写入
}

void fillCache(Cache *c, URL *url, char *data)
{
    // 填充缓存项
    writeBegin(c); // 开始写入
    c->empty = 0; // 设置为非空
    urlCopy(&c->url, url); // 复制URL
    strcpy(c->data, data); // 复制数据
    writeEnd(c); // 结束写入
    updateLRU(c); // 更新LRU值
}

bool urlEqual(const URL *a, const URL *b)
{
    // 比较两个URL是否相等
    return strcmp(a->host, b->host) == 0 &&
           strcmp(a->port, b->port) == 0 &&
           strcmp(a->path, b->path) == 0;
}

void urlCopy(URL *a, const URL *b)
{
    // 复制URL
    strcpy(a->host, b->host);
    strcpy(a->port, b->port);
    strcpy(a->path, b->path);
}

void sbuf_init(sbuf_t *sp, int n)
{
    // 初始化共享FIFO缓冲区
    sp->buf = Calloc(n, sizeof(int)); // 分配内存
    sp->n = n; // 设置缓冲区大小
    sp->front = sp->rear = 0; // 初始化队列头和尾
    Sem_init(&sp->mutex, 0, 1); // 初始化互斥信号量
    Sem_init(&sp->slots, 0, n); // 初始化空槽信号量
    Sem_init(&sp->items, 0, 0); // 初始化数据项信号量
}

void sbuf_deinit(sbuf_t *sp)
{
    // 清理共享FIFO缓冲区
    Free(sp->buf); // 释放内存
}

void sbuf_insert(sbuf_t *sp, int item)
{
    // 向共享FIFO缓冲区插入一个元素
    P(&sp->slots); // 等待空槽
    P(&sp->mutex); // 锁定缓冲区
    sp->buf[(++sp->rear) % (sp->n)] = item; // 插入元素
    V(&sp->mutex); // 解锁缓冲区
    V(&sp->items); // 增加数据项
}

int sbuf_remove(sbuf_t *sp)
{
    // 从共享FIFO缓冲区移除一个元素
    int item;
    P(&sp->items); // 等待数据项
    P(&sp->mutex); // 锁定缓冲区
    item = sp->buf[(++sp->front) % (sp->n)]; // 移除元素
    V(&sp->mutex); // 解锁缓冲区
    V(&sp->slots); // 增加空槽
    return item;
}

void readBegin(Cache *c)
{
    // 开始读取缓存项
    P(&c->mutex); // 锁定缓存项
    if (++c->read_cnt == 1) // 如果是第一个读者，锁定写者
        P(&c->w);
    V(&c->mutex); // 解锁缓存项
}

void readEnd(Cache *c)
{
    // 结束读取缓存项
    P(&c->mutex); // 锁定缓存项
    if (--c->read_cnt == 0) // 如果是最后一个读者，解锁写者
        V(&c->w);
    V(&c->mutex); // 解锁缓存项
}

void writeBegin(Cache *c)
{
    // 开始写入缓存项
    P(&c->w); // 锁定写者
}

void writeEnd(Cache *c)
{
    // 结束写入缓存项
    V(&c->w); // 解锁写者
}